文章使用Unity 2019 LTS
首先要說明的一點,這邊的模型並不是指遊戲中我們常說的角色或場景物件的模型,這裡指的是數學中用來簡化系統的方式,數學模型
在現實生活中,計算「光」是複雜的。為了在電腦中呈現出光照射在物體的模樣,提出了標準光源模型,標準光源模型並不是真實反映實際與光的互動,這些模型則被稱為 「經驗模型」,尤其是在早期的遊戲上,有許多技術上的限制。
經驗模型的方法是將光線進入到攝影機的地方分成4個部分:
在漫反射中,我們使用過環境光以及漫射光,這裡順序來介紹高光反射的部分,也就是模擬表面像是金屬反射的地方。
圖(1)描述了高光反射處是怎麼形成的,n為物體表面的法向量,l為光源方向,v為玩家看向物體的方向,r是根據法向量與光源反射出的方向。
圖(1)
我們需要利用反射向量r與視角方向v之間的夾角來計算出高光反射。
漫反射光中,我們了解到計算可以是在vertex shader或是fragment shader,計算高反射光的模型也是一樣可以在不同的處理階段進行計算,而且在不同地方計算的模型也是有名字的,計算方法寫在vertex shader的叫做Gouraud shading;fragment shader的叫做Phong shading。
以下要講解的是Phong shading的改良版,Blinn-Phong shading,如果想更加了解Phong shading可以參考這篇文(全英文注意),我在下面的範例也會附上Phong的作法。
Blinn-Phong與Phong不同的地方在於Blinn沒有使用反射的方向,而是利用光源方向與視角方向相加後的向量h,再與其表面的法向量形成的夾角進行高光效果,好處在於減少了對於反射光夾角的計算。
圖(2)
Shader "Learning/Blinn-Phong"
{
Properties
{
_Diffuse("Diffuse Color", Color) = (1, 1, 1, 1)
// 高光反射的顏色
_Specular ("Specular Color", Color) = (1, 1, 1, 1)
// 這裡的gloss表示反射光亮點的大小
_Gloss ("Gloss", Range(8.0, 256.0)) = 32.0
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos : SV_POSITION;
float3 normal : NORMAL;
// 需要將物件的頂點由物件空間轉至世界空間
float3 worldPos : TEXCOORD0;
};
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata_base v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldPos = mul(unity_ObjectToWorld, v.vertex.xyz);
o.normal = UnityObjectToWorldNormal(v.normal);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT;
fixed3 worldNormalDir = normalize(i.normal);
fixed3 worldLightDir = UnityWorldSpaceLightDir(i.worldPos);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormalDir, worldLightDir));
// ----Blinn-Phong的作法---
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
// 記得Gloss變數不可為0
// 讀者們可以試試看變為0的效果如何
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormalDir, halfDir)), _Gloss);
// ---Blinn-Phong的作法結束---
// ---Phong的作法---
// fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormalDir));
//
// fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos);
//
// fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
//
// ---Phong的作法結束---
return fixed4(ambient + diffuse + specular, 1);
}
ENDCG
}
}
Fallback "Specular"
}
圖(3)為Blinn-Phong的結果
圖(3) Blinn-Phong Shading
把上面範例的Phong區塊開起來,可以看到Phong的結果 圖(4)
圖(4) Phong Shading
將Phong與Blinn-Phong兩個相互比較會發現,同樣的光澤度(Gloss),Blinn-Phong的高光區會看起來更大更亮。
雖說Blinn-Phong是Phong的改良版,但這並不表示Blinn-Phong是「比較正確」的。如同在電腦圖學領域中一句有名的話:「如果他看起來是對的,那他就是對的」